// -------------------------------------------------------------------------------------------------
// Copyright 2024 Kearn Chen, kearn.chen@aliyun.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -------------------------------------------------------------------------------------------------
// Description :
//             1. Bus Management Unit
// -------------------------------------------------------------------------------------------------

module k0a_core_bmu (
    input  wire         core_clk         ,
    input  wire         core_rstn        ,

    output reg          bus_avalid       ,
    input  wire         bus_aready       ,
    output wire         bus_write        ,
    output wire [17:0]  bus_addr         ,
    output wire [3:0]   bus_strb         ,
    output wire [31:0]  bus_wdata        ,
    input  wire         bus_rvalid       ,
    output wire         bus_rready       ,
    input  wire [31:0]  bus_rdata        ,

    input  wire         idu2bmu_pc_set   ,
    input  wire [17:0]  idu2bmu_pc_new   ,
    output wire         bmu2idu_pc_ack   ,

    input  wire         idu2bmu_ls_req   ,
    input  wire         idu2bmu_ls_cmd   ,
    input  wire [1:0]   idu2bmu_ls_size  ,
    input  wire [19:0]  idu2bmu_ls_addr  ,
    input  wire [31:0]  idu2bmu_ls_wdata ,
    output wire [31:0]  bmu2idu_ls_rdata ,

    output wire         bmu2idu_valid    ,
    output wire [31:0]  bmu2idu_instr    ,
    output reg  [17:0]  bmu2idu_pc_cur   ,
    output wire [17:0]  bmu2idu_pc_nxt
);

reg             discard;
reg             load_data;
reg  [17:0]     prefetch_pc;

reg             instr_wsel;
reg             instr_rsel;
reg  [1:0]      instr_size;
reg  [31:0]     instr_buf0;
reg  [31:0]     instr_buf1;

wire bus_addr_fire = bus_avalid & bus_aready;
wire bus_resp_fire = bus_rvalid & bus_rready;
wire bus_data_fire = bus_resp_fire & ~discard &  load_data;
wire bus_inst_fire = bus_resp_fire & ~discard & ~load_data;

wire bus_load_fire  = idu2bmu_ls_req & ~idu2bmu_ls_cmd & bus_data_fire;
wire bus_store_fire = idu2bmu_ls_req &  idu2bmu_ls_cmd & bus_addr_fire;
wire instr_out_fire = bmu2idu_valid & (bus_store_fire | bus_load_fire | ~idu2bmu_ls_req);

wire byte_at_0 = idu2bmu_ls_size[1] | idu2bmu_ls_size[0] & ~idu2bmu_ls_addr[1] | idu2bmu_ls_addr[1:0] == 2'd0;
wire byte_at_1 = idu2bmu_ls_size[1] | idu2bmu_ls_size[0] & ~idu2bmu_ls_addr[1] | idu2bmu_ls_addr[1:0] == 2'd1;
wire byte_at_2 = idu2bmu_ls_size[1] | idu2bmu_ls_size[0] &  idu2bmu_ls_addr[1] | idu2bmu_ls_addr[1:0] == 2'd2;
wire byte_at_3 = idu2bmu_ls_size[1] | idu2bmu_ls_size[0] &  idu2bmu_ls_addr[1] | idu2bmu_ls_addr[1:0] == 2'd3;

wire pc_set_fire = idu2bmu_pc_set & bmu2idu_pc_ack;

assign bmu2idu_pc_ack = instr_out_fire;

assign bus_write = idu2bmu_ls_cmd;

assign bus_strb  = {byte_at_3, byte_at_2, byte_at_1, byte_at_0};

assign bus_addr  = idu2bmu_ls_req & ~load_data ? idu2bmu_ls_addr[19:2] : prefetch_pc;

assign bus_wdata = idu2bmu_ls_wdata;

assign bus_rready = 1'b1;

assign bmu2idu_ls_rdata = bus_rdata;

assign bmu2idu_valid = |instr_size;

assign bmu2idu_instr = instr_rsel ? instr_buf1 : instr_buf0;

assign bmu2idu_pc_nxt = bmu2idu_pc_cur + 1'b1;

always @(posedge core_clk or negedge core_rstn)
begin
    if(!core_rstn)
        discard <= 1'b0;
    else if(pc_set_fire)
        discard <= 1'b1;
    else if(bus_addr_fire)
        discard <= 1'b0;
end

always @(posedge core_clk or negedge core_rstn)
begin
    if(!core_rstn)
        load_data <= 1'b0;
    else if(bus_addr_fire & idu2bmu_ls_req & ~load_data & ~idu2bmu_ls_cmd)
        load_data <= 1'b1;
    else if(bus_data_fire)
        load_data <= 1'b0;
end

always @(posedge core_clk or negedge core_rstn)
begin
    if(!core_rstn)
        prefetch_pc <= 18'd0;
    else if(pc_set_fire)
        prefetch_pc <= idu2bmu_pc_new;
    else if(bus_addr_fire & (~idu2bmu_ls_req | load_data))
        prefetch_pc <= prefetch_pc + 1'b1;
end

always @(posedge core_clk or negedge core_rstn)
begin
    if(!core_rstn)
        bus_avalid <= 1'b0;
    else
        bus_avalid <= 1'b1;
end

always @(posedge core_clk or negedge core_rstn)
begin
    if(!core_rstn)
        bmu2idu_pc_cur <= 18'd0;
    else if(pc_set_fire)
        bmu2idu_pc_cur <= idu2bmu_pc_new;
    else if(instr_out_fire)
        bmu2idu_pc_cur <= bmu2idu_pc_nxt;
end

always @(posedge core_clk or negedge core_rstn)
begin
    if(!core_rstn)
        instr_wsel <= 1'b0;
    else if(pc_set_fire)
        instr_wsel <= 1'b0;
    else if(bus_inst_fire)
        instr_wsel <= ~instr_wsel;
end

always @(posedge core_clk or negedge core_rstn)
begin
    if(!core_rstn)
        instr_rsel <= 1'b0;
    else if(pc_set_fire)
        instr_rsel <= 1'b0;
    else if(instr_out_fire)
        instr_rsel <= ~instr_rsel;
end

always @(posedge core_clk or negedge core_rstn)
begin
    if(!core_rstn)
        instr_size <= 2'd0;
    else if(pc_set_fire)
        instr_size <= 2'd0;
    else
        instr_size <= instr_size + bus_inst_fire - instr_out_fire;
end

always @(posedge core_clk)
begin
    if(~instr_wsel & bus_inst_fire)
        instr_buf0 <= bus_rdata;
end

always @(posedge core_clk)
begin
    if(instr_wsel & bus_inst_fire)
        instr_buf1 <= bus_rdata;
end

endmodule
